package com.cblz.bd.server.core.interceptor;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cblz.bd.common.core.constant.RedisConstant;
import com.cblz.bd.common.core.entity.BaSecurityDataRecycle;
import com.cblz.bd.common.core.entity.BaSecurityDataRecycleLog;
import com.cblz.bd.common.core.exception.BdAdminException;
import com.cblz.bd.common.core.utils.BdAdminUtil;
import com.cblz.bd.redis.spring.boot.starter.service.RedisService;
import com.cblz.bd.server.core.service.BaSecurityDataRecycleLogService;
import com.cblz.bd.server.core.service.BaSecurityDataRecycleService;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import io.vavr.control.Option;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;

/**
 * {@code @Name:} BaSecurityDataRecyInterceptor
 * {@code @Description: } 数据安全处理拦截器
 * {@code @Author:} by yanjieping
 * {@code @CreateDate:} 2022/12/2 - 周五
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class BaSecurityDataRecyInterceptor implements HandlerInterceptor {

    private static List<String> deleteDatas = new ArrayList<>();
    private static BaSecurityDataRecycle baSecurityDataRecycle;
    private final BaSecurityDataRecycleService baSecurityDataRecycleService;
    private final BaSecurityDataRecycleLogService baSecurityDataRecycleLogService;
    private final RedisService redisService;
    private final ApplicationContext applicationContext;

    /**
     * Interception point before the execution of a handler. Called after
     * HandlerMapping determined an appropriate handler object, but before
     * HandlerAdapter invokes the handler.
     * <p>DispatcherServlet processes a handler in an execution chain, consisting
     * of any number of interceptors, with the handler itself at the end.
     * With this method, each interceptor can decide to abort the execution chain,
     * typically sending an HTTP error or writing a custom response.
     * <p><strong>Note:</strong> special considerations apply for asynchronous
     * request processing. For more details see
     * {@link AsyncHandlerInterceptor}.
     * <p>The default implementation returns {@code true}.
     *
     * @param request  current HTTP request
     * @param response current HTTP response
     * @param handler  chosen handler to execute, for type and/or instance evaluation
     * @return {@code true} if the execution chain should proceed with the
     * next interceptor or the handler itself. Else, DispatcherServlet assumes
     * that this interceptor has already dealt with the response itself.
     * @throws Exception in case of errors
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        deleteDatas.clear();

        Tuple2<Boolean, BaSecurityDataRecycle> dataRecycleCol = isSecurityDataRecycleCol(request.getRequestURI(), request.getMethod());
        if (Boolean.TRUE.equals(dataRecycleCol._1)) {
            //获取字符流，读取数据
            BufferedReader reader = request.getReader();
            Map<String, List<Integer>> ids = (Map<String, List<Integer>>) JSONUtil.parse(reader);
            //反射创建serviceImpl实体对象
            Class<?> serviceImplType;
            try {
                serviceImplType = Class.forName(dataRecycleCol._2.getServiceImplPackage() + BdAdminUtil.underscoreToCamel(dataRecycleCol._2.getDataTable()) + "ServiceImpl");
            } catch (ClassNotFoundException e) {
                throw new BdAdminException("找不到路径为「 " + dataRecycleCol._2.getServiceImplPackage() + BdAdminUtil.underscoreToCamel(dataRecycleCol._2.getDataTable()) + "ServiceImpl 」的类");
            }
            Option.of(ids.get("ids")).getOrElseThrow(() -> {
                throw new BdAdminException("删除数据异常,ids参数为空");
            }).forEach(id -> {
                Method[] methods = serviceImplType.getMethods();
                Set<Method> methodSet = Arrays.stream(methods).filter(method -> method.getName().equals("getById")).collect(Collectors.toSet());
                methodSet.forEach(method -> {
                    try {
                        //在ApplicationContext中根据class取出已实例化的bean
                        var invoke = method.invoke(applicationContext.getBean(serviceImplType), id);
                        deleteDatas.add(JSONUtil.toJsonStr(invoke));
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                        throw new BdAdminException("删除数据异常,调用方法失败");
                    }
                });
            });
            return HandlerInterceptor.super.preHandle(request, response, handler);
        }

        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    /**
     * 请求处理完之后的处理，抛出异常不执行
     *
     * @param request      current HTTP request
     * @param response     current HTTP response
     * @param handler      the handler (or {@link HandlerMethod}) that started asynchronous
     *                     execution, for type and/or instance examination
     * @param modelAndView the {@code ModelAndView} that the handler returned
     *                     (can also be {@code null})
     * @throws Exception
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        Tuple2<Boolean, BaSecurityDataRecycle> dataRecycleCol = isSecurityDataRecycleCol(request.getRequestURI(), request.getMethod());
        if (Boolean.TRUE.equals(dataRecycleCol._1) && Objects.nonNull(baSecurityDataRecycle)) {
            deleteDatas.forEach(data -> {
                BaSecurityDataRecycleLog baSecurityDataRecycleLog = new BaSecurityDataRecycleLog()
                        .setAdminId(Integer.parseInt(StpUtil.getLoginId().toString()))
                        .setRecycleId(baSecurityDataRecycle.getId())
                        .setData(data)
                        .setDataTable(baSecurityDataRecycle.getDataTable())
                        .setPrimaryKey(baSecurityDataRecycle.getPrimaryKey())
                        .setIsRestore(0)
                        .setIp(BdAdminUtil.getIPAddress(request))
                        .setUseragent(request.getHeader("user-agent"))
                        .setCreatetime(LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")));

                baSecurityDataRecycleLogService.save(baSecurityDataRecycleLog);

                log.info("\n删除数据记录：\n\t访问路径:{}\n\t删除数据:{}", request.getRequestURI(), data);
            });
        }
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    /**
     * 判断当前访问路径是否为安全数据控制层
     *
     * @param requestURI    访问路径
     * @param requestMethod 访问方法
     * @return 判断结果和对应的实体类信息
     */
    public Tuple2<Boolean, BaSecurityDataRecycle> isSecurityDataRecycleCol(String requestURI, String requestMethod) {
        String hashKey = "securityDataRecycleHashMap";
        String key = RedisConstant.SECURITY_DATA_RECYCLE_PREFIX.getValue() + hashKey;
        HashMap<String, BaSecurityDataRecycle> securityDataRecycleHashMap = Option.of((HashMap<String, BaSecurityDataRecycle>) redisService.hget(key, hashKey)).getOrElse(HashMap::new);
        if (Objects.isNull(securityDataRecycleHashMap) || securityDataRecycleHashMap.isEmpty()) {
            Map<String, Object> baSecurityDataRecycleMap = baSecurityDataRecycleService.index(Tuple.of(new Page<>(1, 10000), new QueryWrapper<BaSecurityDataRecycle>().eq("status", 1), new BaSecurityDataRecycle()));

            Option.of(baSecurityDataRecycleMap.get("list")).forEach(baSecurityDataRecycles -> {
                List<BaSecurityDataRecycle> baSecurityDataRecycleList = (List<BaSecurityDataRecycle>) baSecurityDataRecycles;
                baSecurityDataRecycleList.forEach(baSecurityDataRecycle -> securityDataRecycleHashMap.put(baSecurityDataRecycle.getController(), baSecurityDataRecycle));
            });

            if (securityDataRecycleHashMap.isEmpty()) {
                redisService.hset(key, hashKey, "", 10L);
            } else {
                if (!redisService.hset(key, hashKey, securityDataRecycleHashMap, 3600L))
                    throw new BdAdminException("缓存「securityDataRecycleHashMap 」失败,请查看Redis服务是否异常");
            }
        }
        if (securityDataRecycleHashMap.containsKey(requestURI) && StringUtils.equals(requestMethod, "DELETE")) {
            baSecurityDataRecycle = securityDataRecycleHashMap.get(requestURI);
            return Tuple.of(Boolean.TRUE, baSecurityDataRecycle);
        }
        return Tuple.of(Boolean.FALSE, null);
    }

}
